package com.m31skytech.ElasticTimer.timer;

import com.m31skytech.ElasticTimer.ElasticTimerCuratorHandleService;
import com.m31skytech.ElasticTimer.ElasticTimerRegister;
import com.m31skytech.ElasticTimer.service.ETRSCheckRes;
import com.m31skytech.ElasticTimer.utils.CollectionsHelper;
import org.quartz.*;
import org.quartz.impl.StdScheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;


public class ElasticTimerQuarztHandleProxy implements ApplicationListener<ContextRefreshedEvent>, InitializingBean {
    private Executor executor= Executors.newFixedThreadPool(10);
    static final String IP_REGEX="IP_REGEX";
    static final String INVOKE_DEFINETYPE="INVOKE_DEFINETYPE";
    static final String INVOKE_DEFINETYPE_DEFAULT="BEAN_CLASS";
    static final String INVOKE_DEFINETYPE_VALUE="INVOKE_DEFINETYPE_VALUE";
    static final String INVOKE_DEFINETYPE_VALUE_DEFAULT="*";
    static final String CORN_TRIGGER_CACHED_STOP="CORN_TRIGGER_CACHED_STOP";

    static final String STATUS_RUNNING="true";
    static final String STATUS_STOP="false";
    static final String IP_ALL=".*";
    static final String METHOD_INVOKE_BEAN_TAG="methodInvoker";
    static final String CONCURRENT_TRUE="true";
    static final int MAX_TIME_WAITTING_SYNC=60;
    static final long WAIT_TIMEONCE=1000L;

    static final List<String> avalableTriggerTypes=new ArrayList<String>(
            Arrays.asList(new String[]{
                    CronTriggerBean.class.getName()
                    }
            ));
    static final List<String> avalableRunningTypes=new ArrayList<String>(
            Arrays.asList(new String[]{
                    STATUS_RUNNING,STATUS_STOP
                    }
            ));
    static final List<String> avalableInvokeType=new ArrayList<>(
            Arrays.asList(new String[]{
                    ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_CLASS,
                    ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_ID,
                    ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_STATIC_CLASS
                    }
            )
    );



    static Logger logger= LoggerFactory.getLogger(ElasticTimerQuarztHandleProxy.class);
    @Autowired
    private ElasticTimerCuratorHandleService elasticTimerCuratorHandleService;
    private StdScheduler innerSchedule;


    public StdScheduler getInnerSchedule() {
        return innerSchedule;
    }

    public void setInnerSchedule(StdScheduler innerSchedule) {
        this.innerSchedule = innerSchedule;
    }

    public void afterPropertiesSet() throws Exception {
        getInnerSchedule().pauseAll();
    }

    /**获取当前运行中的定时器配置信息
     *
     * @return
     */
    public ElasticTimerConfig getLocalRuntimeElasticTimerConfig() throws Exception {
        ElasticTimerConfig elasticTimerConfig=new ElasticTimerConfig();
        //1、getAllConfig
        for(String group:innerSchedule.getJobGroupNames()){
            for(String job:innerSchedule.getJobNames(group)){
                JobDetail jobDetail=innerSchedule.getJobDetail(job,group);

                Trigger[] triggers= innerSchedule.getTriggersOfJob(job,group);

                if(jobDetail.getJobDataMap().containsKey(CORN_TRIGGER_CACHED_STOP)){
                    CronTriggerBean triggerBean= (CronTriggerBean) jobDetail.getJobDataMap().get(CORN_TRIGGER_CACHED_STOP);
                    ElasticTimerConfig.ElasticTimerSingleConfig singleConfig=new ElasticTimerConfig.ElasticTimerSingleConfig(job, group, triggerBean.getName(), triggerBean.getGroup());
                    singleConfig.setIpRegex(null==jobDetail.getJobDataMap().getString(IP_REGEX)?IP_ALL:jobDetail.getJobDataMap().getString(IP_REGEX));
                    singleConfig.setInvokeDefineTypeArgs(null==jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE_VALUE)?INVOKE_DEFINETYPE_VALUE_DEFAULT
                            :jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE_VALUE));
                    singleConfig.setInvokeDefineType(null==jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE)?INVOKE_DEFINETYPE_DEFAULT
                            :jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE));
                    singleConfig.setIsRunning(STATUS_STOP);  //all exits must be running status, TODO performence later
                    singleConfig.setJobNeedConcurrent(String.valueOf(!jobDetail.isStateful()));
                    singleConfig.setTriggerType(triggerBean.getClass().getName());
                    singleConfig.setTriggerCornExpress(triggerBean.getCronExpression());
                    if(jobDetail.getJobDataMap().containsKey(METHOD_INVOKE_BEAN_TAG)){
                        MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean=
                                ((MethodInvokingJobDetailFactoryBean)jobDetail.getJobDataMap().get(METHOD_INVOKE_BEAN_TAG));
                        singleConfig.setInvokeDefineTypeArgs(methodInvokingJobDetailFactoryBean.getTargetClass().getName());
                        singleConfig.setInvokeMethodName(methodInvokingJobDetailFactoryBean.getTargetMethod());
                    }
                    elasticTimerConfig.getElasticTimerSingleConfigMap().put(
                            ElasticTimerConfig.parseKey(group,job,triggerBean.getGroup(),triggerBean.getName()),singleConfig);
                }


                for(Trigger trigger:triggers){
                    ElasticTimerConfig.ElasticTimerSingleConfig singleConfig=new ElasticTimerConfig.ElasticTimerSingleConfig(job, group, trigger.getName(), trigger.getGroup());
                    singleConfig.setIpRegex(null==jobDetail.getJobDataMap().getString(IP_REGEX)?IP_ALL:jobDetail.getJobDataMap().getString(IP_REGEX));
                    singleConfig.setInvokeDefineTypeArgs(null==jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE_VALUE)?INVOKE_DEFINETYPE_VALUE_DEFAULT
                            :jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE_VALUE));
                    singleConfig.setInvokeDefineType(null==jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE)?INVOKE_DEFINETYPE_DEFAULT
                            :jobDetail.getJobDataMap().getString(INVOKE_DEFINETYPE));
                    singleConfig.setIsRunning(STATUS_RUNNING);  //all exits must be running status, TODO performence later
                    singleConfig.setJobNeedConcurrent(String.valueOf(!jobDetail.isStateful()));
                    singleConfig.setTriggerType(trigger.getClass().getName());
                    if(trigger instanceof CronTrigger){
                        singleConfig.setTriggerCornExpress(
                                ((CronTrigger)(trigger)).getCronExpression());
                    }else{
                        singleConfig.setTriggerCornExpress("-");
                    }
                    if(jobDetail.getJobDataMap().containsKey(METHOD_INVOKE_BEAN_TAG)){
                        MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean=
                                ((MethodInvokingJobDetailFactoryBean)jobDetail.getJobDataMap().get(METHOD_INVOKE_BEAN_TAG));
                        singleConfig.setInvokeDefineTypeArgs(methodInvokingJobDetailFactoryBean.getTargetClass().getName());
                        singleConfig.setInvokeMethodName(methodInvokingJobDetailFactoryBean.getTargetMethod());
                    }
                    elasticTimerConfig.getElasticTimerSingleConfigMap().put(
                            ElasticTimerConfig.parseKey(group,job,trigger.getGroup(),trigger.getName()),singleConfig);
                }
            }
        }
        return elasticTimerConfig;
    }
    public ElasticTimerConfig getRemoteElasticTimerConfig() throws Exception{
        return elasticTimerCuratorHandleService.getRemoteConfig();
    }



    public ETRSCheckRes checkServiceSync(List<ElasticTimerCuratorHandleService.Service> services) throws Exception {
        ETRSCheckRes etrsCheckRes=new ETRSCheckRes();
        etrsCheckRes.setTotalNodeCount(services.size());

        ElasticTimerConfig elasticTimerConfigRemote=getRemoteElasticTimerConfig();
        if(null==elasticTimerConfigRemote){
            etrsCheckRes.setResultMessage("Remote undefine,please push config to Remote.");
            elasticTimerConfigRemote=new ElasticTimerConfig();
        }
        etrsCheckRes.setRemoteConfig(elasticTimerConfigRemote);
        for (ElasticTimerCuratorHandleService.Service service : services) {
            ElasticTimerConfig elasticTimerConfigTemp=elasticTimerCuratorHandleService.getTargetIpLocalElasticTimerConfig(service);
            if(elasticTimerConfigRemote.equals(elasticTimerConfigTemp)){
                etrsCheckRes.getSynced().add(service.getHost()+"#"+service.getPort());
                etrsCheckRes.setSyncedNodeCount(etrsCheckRes.getSyncedNodeCount()+1);
            }else{
                etrsCheckRes.setUnSyncedNodeCount(etrsCheckRes.getUnSyncedNodeCount()+1);
                etrsCheckRes.getUnSync().put(service.getHost()+"#"+service.getPort(),analysisDiffFromRemote(elasticTimerConfigRemote,elasticTimerConfigTemp));
            }
        }
        if(services.size()==etrsCheckRes.getSynced().size()){
            etrsCheckRes.setStatus(ETRSCheckRes.STATUS_ALLSYNC);
        }else if(etrsCheckRes.getSynced().size()>0){
            etrsCheckRes.setStatus(ETRSCheckRes.STATUS_PARTSYNC);
        }else{
            etrsCheckRes.setStatus(ETRSCheckRes.STATUS_NONESYNC);
        }
        return etrsCheckRes;
    }

    private ElasticTimerConfig analysisDiffFromRemote( ElasticTimerConfig remote,ElasticTimerConfig elasticTimerConfigTemp){
        CollectionsHelper.Merge merge=CollectionsHelper.merge(remote.getElasticTimerSingleConfigMap().keySet(),elasticTimerConfigTemp.getElasticTimerSingleConfigMap().keySet());
        for (String add : merge.getAddSet()) {
            elasticTimerConfigTemp.getElasticTimerSingleConfigMap().put("+++/"+add,elasticTimerConfigTemp.getElasticTimerSingleConfigMap().get(add));
            elasticTimerConfigTemp.getElasticTimerSingleConfigMap().remove(add);
        }
        for(String del: merge.getDelSet()){
            elasticTimerConfigTemp.getElasticTimerSingleConfigMap().put("---/"+del,remote.getElasticTimerSingleConfigMap().get(del));
        }
        for(String same:merge.getSameSet()){
            ElasticTimerConfig.ElasticTimerSingleConfig configRemote=remote.getElasticTimerSingleConfigMap().get(same);
            ElasticTimerConfig.ElasticTimerSingleConfig configLocal=elasticTimerConfigTemp.getElasticTimerSingleConfigMap().get(same);
            if(configLocal.getTriggerGroup().equals(configRemote.getTriggerGroup()))configLocal.setTriggerGroup("===");
            if(configLocal.getTriggerName().equals(configRemote.getTriggerName()))configLocal.setTriggerName("===");
            if(configLocal.getJobName().equals(configRemote.getJobName()))configLocal.setJobName("===");
            if(configLocal.getJobGroup().equals(configRemote.getJobGroup()))configLocal.setJobGroup("===");
            if(configLocal.getJobNeedConcurrent().equals(configRemote.getJobNeedConcurrent()))configLocal.setJobNeedConcurrent("===");
            if(configLocal.getIsRunning().equals(configRemote.getIsRunning()))configLocal.setIsRunning("===");
            if(configLocal.getInvokeDefineTypeArgs().equals(configRemote.getInvokeDefineTypeArgs()))configLocal.setInvokeDefineTypeArgs("===");
            if(configLocal.getInvokeMethodName().equals(configRemote.getInvokeMethodName()))configLocal.setInvokeMethodName("===");
            if(configLocal.getInvokeDefineType().equals(configRemote.getInvokeDefineType()))configLocal.setInvokeDefineType("===");
            if(configLocal.getIpRegex().equals(configRemote.getIpRegex()))configLocal.setIpRegex("===");
            if(configLocal.getTriggerCornExpress().equals(configRemote.getTriggerCornExpress()))configLocal.setTriggerCornExpress("===");
            if(configLocal.getTriggerType().equals(configRemote.getTriggerType()))configLocal.setTriggerType("===");
        }
        return elasticTimerConfigTemp;
    }


    /**同步配置项信息
     *
     * @param local
     * @param remote
     */
    public void syncConfigFromRemote(final ElasticTimerConfig local, ElasticTimerConfig remote) throws InterruptedException {
        CollectionsHelper.Merge merge=CollectionsHelper.merge(local.getElasticTimerSingleConfigMap().keySet(),remote.getElasticTimerSingleConfigMap().keySet());
        CountDownLatch countDownLatch=new CountDownLatch(merge.getAddSet().size()+merge.getSameSet().size()+merge.getAddSet().size());
        // HANDLE MODIFY ATTRIBUTE
        if(!merge.getSameSet().isEmpty()){
            for(final String sameTag:merge.getSameSet()){
                //continue if same
                if(local.getElasticTimerSingleConfigMap().get(sameTag).equals(remote.getElasticTimerSingleConfigMap().get(sameTag))){
                    countDownLatch.countDown();
                    continue;
                }
                //modify
                //
                final ElasticTimerConfig.ElasticTimerSingleConfig sameTagConfig =remote.getElasticTimerSingleConfigMap().get(sameTag);
                registerNewSchedule(local,sameTag,sameTagConfig,STAGE.SYNC,countDownLatch);
            }
        }
        // HANDLE ADD ATTRIBUTE
        if(!merge.getAddSet().isEmpty()){
            for(String addTag:merge.getAddSet()){
                final ElasticTimerConfig.ElasticTimerSingleConfig addTagConfig =remote.getElasticTimerSingleConfigMap().get(addTag);
                registerNewSchedule(local,addTag,addTagConfig,STAGE.ADD,countDownLatch);
            }
        }
        // HANDLE DEL ATTRIBUTE
        if(!merge.getDelSet().isEmpty()){
            for(String delTag:merge.getDelSet()){
                ElasticTimerConfig.ElasticTimerSingleConfig delTagConfig =remote.getElasticTimerSingleConfigMap().get(delTag);
                registerNewSchedule(local,delTag,delTagConfig,STAGE.DEL,countDownLatch);
            }
        }
        countDownLatch.await();
        logger.info("sync done.");
    }

    /**
     *
     * @param originConfig
     * @param keyTag
     * @param tarConfig
     */
    public void registerNewSchedule(final ElasticTimerConfig originConfig, final String keyTag, final ElasticTimerConfig.ElasticTimerSingleConfig tarConfig, final STAGE statge,final CountDownLatch countDownLatch){
        if(!avalableRunningTypes.contains(tarConfig.getIsRunning())){
            logger.info("skip build ,unknow running status >>"+tarConfig.getIsRunning());
            countDownLatch.countDown();
            return ;
        }

        //validating exis target invokeBean/Class
        if(!validatingInvokeMethods(tarConfig)){
            logger.info("skip build ,use default origin");
            countDownLatch.countDown();
            return ;
        }

        if(STAGE.SYNC==statge||STAGE.DEL==statge){
            try{
                getInnerSchedule().pauseTrigger(tarConfig.getTriggerName(),tarConfig.getTriggerGroup());
                getInnerSchedule().unscheduleJob(tarConfig.getTriggerName(),tarConfig.getTriggerGroup());
                getInnerSchedule().deleteJob(tarConfig.getJobName(),tarConfig.getJobGroup());
            }catch (Exception e){
                logger.error("error when clear old job >> "+ElasticTimerConfig.parseKey(tarConfig.getJobGroup(),tarConfig.getJobName(),tarConfig.getTriggerGroup(),tarConfig.getTriggerName()));
                if(STAGE.SYNC==statge){
                    registerOriginSchedule(originConfig,keyTag,tarConfig);
                }
            }
        }

            if(STAGE.SYNC==statge){//READY TO UPDATE

            }else if(STAGE.DEL==statge){
                logger.info("success stop job detail >>"+tarConfig.getJobGroup()+"_"+tarConfig.getJobName());
                countDownLatch.countDown();
                return;
            }
            else if(STAGE.ADD==statge){//READY TO UPDATE
//                logger.info("stop define job no need to add >>"+tarConfig.getJobGroup()+"_"+tarConfig.getJobName());
//                return;
            }else{
                logger.warn("unknow stage."+statge);
                countDownLatch.countDown();
                return;
            }
            //DEL STOP HERE

            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean=new MethodInvokingJobDetailFactoryBean();
                        methodInvokingJobDetailFactoryBean.setName(tarConfig.getJobName());
                        methodInvokingJobDetailFactoryBean.setConcurrent(CONCURRENT_TRUE.equals(tarConfig.getJobNeedConcurrent())?true:false);

                        defineTargetBeanObject(methodInvokingJobDetailFactoryBean,tarConfig.getInvokeDefineTypeArgs(),tarConfig.getInvokeDefineType());

                        methodInvokingJobDetailFactoryBean.setTargetMethod(tarConfig.getInvokeMethodName());
                        methodInvokingJobDetailFactoryBean.afterPropertiesSet();
                        final JobDetail jobDetail=methodInvokingJobDetailFactoryBean.getObject();
                        jobDetail.getJobDataMap().put(IP_REGEX,tarConfig.getIpRegex());
                        jobDetail.getJobDataMap().put(INVOKE_DEFINETYPE,tarConfig.getInvokeDefineType());
                        jobDetail.getJobDataMap().put(INVOKE_DEFINETYPE_VALUE,tarConfig.getInvokeDefineTypeArgs());

                        //build corn
                        final CronTriggerBean trigger0 = new CronTriggerBean();
                        trigger0.setBeanName(tarConfig.getTriggerName());
                        trigger0.setGroup(tarConfig.getTriggerGroup());
                        trigger0.setCronExpression(tarConfig.getTriggerCornExpress());
                        trigger0.setJobDetail(jobDetail);
                        trigger0.afterPropertiesSet();
                        logger.info("ready to sync JobDetail ");
                        int currentTimesWaitting=0;
                        while (true){
                            currentTimesWaitting++;
                            if(currentTimesWaitting>MAX_TIME_WAITTING_SYNC){
                                throw new Exception("gt maxtime waitting >> "+currentTimesWaitting);
                            }
                            logger.info(" sync check waitting for >> " +ElasticTimerConfig.parseKey(jobDetail.getGroup(),jobDetail.getName(),trigger0.getGroup(),trigger0.getName()));
                            try {
                                Thread.sleep(WAIT_TIMEONCE);
                                List<JobExecutionContext> listTarget=getInnerSchedule().getCurrentlyExecutingJobs();
                                boolean stillRunning=false;
                                for (JobExecutionContext jobExecutionContext : listTarget) {
                                    if(jobExecutionContext.getJobDetail().getGroup().equals(tarConfig.getJobGroup())
                                            &&jobExecutionContext.getJobDetail().getName().equals(tarConfig.getJobName())){
                                        stillRunning=true;
                                        break;
                                    }
                                }
                                if(!stillRunning){
                                    break;
                                }
                                //check if done
                            } catch (InterruptedException e) {
                                logger.error("error happend when check sync status >> "+ElasticTimerConfig.parseKey(jobDetail.getGroup(),jobDetail.getName(),trigger0.getGroup(),trigger0.getName())
                                        ,e);
                            }
                        }
                        logger.info("sync success >>"+ElasticTimerConfig.parseKey(jobDetail.getGroup(),jobDetail.getName(),trigger0.getGroup(),trigger0.getName()));
                        if(
                                STATUS_STOP.equals(tarConfig.getIsRunning())
                                        ||!inTargetIp(tarConfig.getIpRegex())

                        ){
                            jobDetail.getJobDataMap().put(CORN_TRIGGER_CACHED_STOP,trigger0);
                            getInnerSchedule().addJob(jobDetail,true);
                            //cached cornbean
//                            getInnerSchedule().pauseJob(jobDetail.getName(),jobDetail.getGroup());
//                            getInnerSchedule().scheduleJob(jobDetail,trigger0);
                        }else {
                            getInnerSchedule().scheduleJob(jobDetail,trigger0);
                        }
                    } catch (Exception e) {
                        logger.error("error when build new Job ,e is >> ",e);
                        if(STAGE.SYNC.equals(statge)){
                            registerOriginSchedule(originConfig,keyTag,tarConfig);
                        }
                    }finally {
                        countDownLatch.countDown();
                    }
                }
            });
    }




    public boolean inTargetIp(String avalableIpRegexDefine){
        String localIp = "";
        try{
            InetAddress LocalIP = InetAddress.getLocalHost();
            localIp = LocalIP.getHostAddress();
            return Pattern.matches(avalableIpRegexDefine,localIp+":"+elasticTimerCuratorHandleService.getCurrentDubboPort());
        }catch (Exception e){
            logger.error("error when check ip avalable >> ",e);
            return false;
        }

    }

    /**
     * false:should not add/sync
     * true: avalabel add/sync
     * @param singleConfig
     * @return
     */
    public boolean validatingInvokeMethods(ElasticTimerConfig.ElasticTimerSingleConfig singleConfig){
        //
        if(!avalableInvokeType.contains(singleConfig.getInvokeDefineType())){
            logger.error("unknow invoke define type >> "+singleConfig.getInvokeDefineType());
            return false;
        }
        //
        Class targetClazz=null;
        if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_ID.equals(singleConfig.getInvokeDefineType())){
            if(!Arrays.asList(ElasticTimerRegister.getApplicationContext().getBeanDefinitionNames()).contains(singleConfig.getInvokeDefineTypeArgs())){
                logger.error("not any bean name >> "+singleConfig.getInvokeDefineTypeArgs());
                return false;
            }
            targetClazz=ElasticTimerRegister.getApplicationContext().getBean(singleConfig.getInvokeDefineTypeArgs()).getClass();
        }
        if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_CLASS.equals(singleConfig.getInvokeDefineType())
        || ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_STATIC_CLASS.equals(singleConfig.getInvokeDefineType())){
            try {
                targetClazz=Class.forName(singleConfig.getInvokeDefineTypeArgs());
            } catch (ClassNotFoundException e) {
                logger.error("class define not found >> "+singleConfig.getInvokeDefineTypeArgs());
                return false;
            }

        }
        try {
            Method method=ReflectionUtils.findMethod(targetClazz,singleConfig.getInvokeMethodName());
            if(!Modifier.isPublic(method.getModifiers())){
                logger.error("target method must be public modifier,please check >> "+singleConfig.getInvokeDefineTypeArgs()+"."+singleConfig.getInvokeMethodName());
                return false;
            }
            if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_STATIC_CLASS.equals(singleConfig.getInvokeDefineType())
            && !Modifier.isStatic(method.getModifiers())){
                logger.error("use static class target method must be static, please check >> "+singleConfig.getInvokeDefineTypeArgs()+"."+singleConfig.getInvokeMethodName());
                return false;
            }
            return true;
        }catch (Exception e){
            logger.error("check clazz.method error >> "+singleConfig.getInvokeDefineTypeArgs()+"."+singleConfig.getInvokeMethodName()+"|"+e.getLocalizedMessage());
            return false;
        }
    }




    public void registerOriginSchedule(ElasticTimerConfig originConfig, String keyTag,ElasticTimerConfig.ElasticTimerSingleConfig tarConfig){
        try{
            logger.info("ready to restart origin job>>"+ElasticTimerConfig.parseKey(tarConfig.getJobGroup(),tarConfig.getJobName(),tarConfig.getTriggerGroup(),tarConfig.getTriggerName()));
            ElasticTimerConfig.ElasticTimerSingleConfig sameTagConfigLocal =originConfig.getElasticTimerSingleConfigMap().get(keyTag);
            MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean=new MethodInvokingJobDetailFactoryBean();
            methodInvokingJobDetailFactoryBean.setName(sameTagConfigLocal.getJobName());
            methodInvokingJobDetailFactoryBean.setConcurrent(CONCURRENT_TRUE.equals(sameTagConfigLocal.getJobNeedConcurrent())?true:false);
            defineTargetBeanObject(methodInvokingJobDetailFactoryBean,sameTagConfigLocal.getInvokeDefineTypeArgs(),sameTagConfigLocal.getInvokeDefineType());
            methodInvokingJobDetailFactoryBean.setTargetMethod(sameTagConfigLocal.getInvokeMethodName());

            methodInvokingJobDetailFactoryBean.afterPropertiesSet();
            final JobDetail jobDetail=methodInvokingJobDetailFactoryBean.getObject();
            jobDetail.getJobDataMap().put(IP_REGEX,sameTagConfigLocal.getIpRegex());
            jobDetail.getJobDataMap().put(INVOKE_DEFINETYPE,sameTagConfigLocal.getInvokeDefineType());
            jobDetail.getJobDataMap().put(INVOKE_DEFINETYPE_VALUE,sameTagConfigLocal.getInvokeDefineTypeArgs());

            CronTriggerBean trigger0 = new CronTriggerBean();
            trigger0.setBeanName(sameTagConfigLocal.getTriggerName());
            trigger0.setGroup(sameTagConfigLocal.getTriggerGroup());
            trigger0.setCronExpression(sameTagConfigLocal.getTriggerCornExpress());
            trigger0.setJobDetail(jobDetail);
            trigger0.afterPropertiesSet();
            getInnerSchedule().scheduleJob(jobDetail,trigger0);
            logger.info("success to restart origin job>>"+ElasticTimerConfig.parseKey(jobDetail.getGroup(),jobDetail.getName(),trigger0.getGroup(),trigger0.getName()));
        }catch (Exception e2){
            logger.error("restart origin job error, please check",e2);
        }
    }





    public void defineTargetBeanObject(MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean, String invokeClassArgsValue,String type) throws Exception {
        if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_CLASS.equals(type)){
            Class clazz=Class.forName(invokeClassArgsValue);
           String[] args= ElasticTimerRegister.getApplicationContext().getBeanNamesForType(clazz);
            logger.info("total bean size "+args.length);
            String names="/";
            for (String arg : args) {
                names+=arg+"/";
            }
            logger.info("total bean value >> "+names);
            if(args.length>0){
                String beanUse=args[0];
                logger.info("default to use bean >> "+beanUse);
                methodInvokingJobDetailFactoryBean.setTargetObject( ElasticTimerRegister.getApplicationContext().getBean(beanUse));
                return;
            }else{
                if(classObjectConcurrentHashMap.containsKey(clazz)){
                    methodInvokingJobDetailFactoryBean.setTargetObject( classObjectConcurrentHashMap.get(clazz));
                    return;
                }else{
                    logger.info("none bean found,start to register bean as Default constructor");
                    classObjectConcurrentHashMap.put(clazz,clazz.newInstance());
                    methodInvokingJobDetailFactoryBean.setTargetObject( classObjectConcurrentHashMap.get(clazz));
                    return;
                }
            }
        }else if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_BEAN_ID.equals(type)){
            methodInvokingJobDetailFactoryBean.setTargetObject( ElasticTimerRegister.getApplicationContext().getBean(invokeClassArgsValue));
            return ;
        }else if(ElasticTimerConfig.ElasticTimerSingleConfig.INVOKE_DEFINE_TYPE_STATIC_CLASS.equals(type)){
            Class clazz=Class.forName(invokeClassArgsValue);
            methodInvokingJobDetailFactoryBean.setTargetClass( clazz);
            return ;
        }else{
            throw new Exception("unknow args type >> "+type);
        }
    }



    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try{
//            logger.info("CuratorCachedClient is running:"+elasticTimerCuratorHandleService.isAvalable());
            //check if has config properties in zkConfig
            boolean ifHasConfigProperties=elasticTimerCuratorHandleService.ifConfigPropertiesPersistentExits();
            if(ifHasConfigProperties){
                logger.info("remote config exist, schedule config sync start.");
                //check if config has modify
                logger.info("start to sync schedule jobs");
//                getInnerSchedule().pauseAll();
                ElasticTimerConfig elasticTimerConfigLocal=getLocalRuntimeElasticTimerConfig();
                ElasticTimerConfig elasticTimerConfigRemote=getRemoteElasticTimerConfig();
                syncConfigFromRemote(elasticTimerConfigLocal,elasticTimerConfigRemote);
                logger.info("success sync all schedule jobs");
            }else{
                logger.info("remote config not exist.");
            }
            logger.info("schedule config sync done.");
        }catch (Exception e){
            logger.error("error when init elastic timer proxy.",e);
        }finally {
            try {
                getInnerSchedule().resumeAll();
            } catch (SchedulerException e) {
                logger.error("error when resumeJobs",e);
            }
        }
    }
    private ConcurrentHashMap<Class,Object> classObjectConcurrentHashMap=new ConcurrentHashMap<>();

    public ConcurrentHashMap<Class, Object> getClassObjectConcurrentHashMap() {
        return classObjectConcurrentHashMap;
    }

    enum STAGE{
        ADD,DEL,SYNC
    }
}




